package com.google.code.fluentjoiner;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Iterator;
import java.util.Map;
import org.hamcrest.Matcher;

public abstract class MapSupplierBuilder<T, U> extends AbstractSupplierBuilder<Map<T, U>>
{
    public static <T, U> MapSupplierBuilder<T, U> indexToKeys(final Supplier<Iterator<U>> supplier, final Function<? super U, T> indexFunction)
    {
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                return Maps.uniqueIndex(Lists.newArrayList(supplier.get()), indexFunction);
            }
        };
    }

    public static <T, U> MapSupplierBuilder<T, U> indexToValues(final Supplier<Iterator<T>> supplier, final Function<? super T, U> indexFunction)
    {
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                final Map<T, U> map = Maps.newHashMap();
                final Iterator<T> iterator = supplier.get();
                while(iterator.hasNext())
                {
                    final T key = iterator.next();
                    Preconditions.checkArgument(!map.containsKey(key), "An entry with the key %s already exists.", key);
                    map.put(key, indexFunction.apply(key));
                }
                return map;
            }
        };
    }

    public static <T, U> MapSupplierBuilder<T, U> fromMap(final Map<T, U> map)
    {
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                return map;
            }
        };
    }

    public final MapSupplierBuilder<T, U> filterKeys(final Matcher<? super T> matcher)
    {
        return filterKeys(PredicateUtils.fromMatcher(matcher));
    }

    public final MapSupplierBuilder<T, U> filterKeys(final Predicate<? super T> predicate)
    {
        final Supplier<Map<T, U>> supplier = getSupplier();
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                return Maps.filterKeys(supplier.get(), predicate);
            }
        };
    }

    public final MapSupplierBuilder<T, U> filterValues(final Matcher<? super U> matcher)
    {
        return filterValues(PredicateUtils.fromMatcher(matcher));
    }

    public final MapSupplierBuilder<T, U> filterValues(final Predicate<? super U> predicate)
    {
        final Supplier<Map<T, U>> supplier = getSupplier();
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                return Maps.filterValues(supplier.get(), predicate);
            }
        };
    }

    public final MapSupplierBuilder<T, U> filterEntries(final Matcher<? super Map.Entry<T, U>> matcher)
    {
        return filterEntries(PredicateUtils.fromMatcher(matcher));
    }

    public final MapSupplierBuilder<T, U> filterEntries(final Predicate<? super Map.Entry<T, U>> predicate)
    {
        final Supplier<Map<T, U>> supplier = getSupplier();
        return new MapSupplierBuilder<T, U>()
        {
            @Override
            protected Map<T, U> getSupplierResult()
            {
                return Maps.filterEntries(supplier.get(), predicate);
            }
        };
    }

    public final <V> MapSupplierBuilder<T, V> transformValues(final Function<? super U, V> function)
    {
        final Supplier<Map<T, U>> supplier = getSupplier();
        return new MapSupplierBuilder<T, V>()
        {
            @Override
            protected Map<T, V> getSupplierResult()
            {
                return Maps.transformValues(supplier.get(), function);
            }
        };
    }

    public final SupplierBuilder<ImmutableMap<T, U>> toImmutableMap()
    {
        final Supplier<Map<T, U>> supplier = getSupplier();
        return new AbstractSupplierBuilder<ImmutableMap<T, U>>()
        {
            @Override
            protected ImmutableMap<T, U> getSupplierResult()
            {
                return ImmutableMap.copyOf(supplier.get());
            }
        };
    }
}